package sample.context.orm;
import java.time.*;
import java.util.*;
import java.util.function.Function;
import javax.persistence.EntityManager;
import javax.persistence.criteria.*;
import javax.persistence.metamodel.Metamodel;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.criterion.MatchMode;
import sample.context.orm.Sort.SortOrder;
/**
* ORM の可変条件を取り扱う CriteriaBuilder ラッパー。
* <p>Criteria の簡易的な取り扱いを可能にします。
* <p>Criteria で利用する条件句は必要に応じて追加してください。
* <p>ビルド結果としての CriteriaQuery は result* メソッドで受け取って下さい。
*/
public class OrmCriteria<T> {
public static final String DefaultAlias = "m";
private final Class<T> clazz;
private final String alias;
private final Metamodel metamodel;
private final CriteriaBuilder builder;
private final CriteriaQuery<T> query;
private final Root<T> root;
private final Set<Predicate> predicates = new LinkedHashSet<>();
private final Set<Order> orders = new LinkedHashSet<>();
/** 指定したEntityクラスにエイリアスを紐付けたCriteriaを生成します。 */
private OrmCriteria(EntityManager em, Class<T> clazz, String alias) {
this.clazz = clazz;
this.metamodel = em.getMetamodel();
this.builder = em.getCriteriaBuilder();
this.query = builder.createQuery(clazz);
this.root = query.from(clazz);
this.alias = alias;
this.root.alias(alias);
}
/** 内部に保有するエンティティクラスを返します。 */
public Class<T> entityClass() {
return clazz;
}
/** エンティティのメタ情報を返します。 */
public Metamodel metamodel() {
return metamodel;
}
/** 内部に保有する CriteriaBuilder を返します。 */
public CriteriaBuilder builder() {
return builder;
}
/** 内部に保有する Root を返します。 */
public Root<T> root() {
return root;
}
/**
* 関連付けを行います。
* <p>引数にはJoin可能なフィールド(@ManyToOne 等)を指定してください。
* <p>Join した要素は呼び出し元で保持して必要に応じて利用してください。
*/
public <Y> Join<T, Y> join(String associationPath) {
return root.join(associationPath);
}
public <Y> Join<T, Y> join(String associationPath, String alias) {
Join<T, Y> v = join(associationPath);
v.alias(alias);
return v;
}
/**
* 組み上げた CriteriaQuery を返します。
* <p>複雑なクエリや集計関数は本メソッドで返却された query を元に追加構築してください。
*/
public CriteriaQuery<T> result() {
return result(q -> q);
}
@SuppressWarnings("unchecked")
public CriteriaQuery<T> result(Function<CriteriaQuery<?>, CriteriaQuery<?>> extension) {
CriteriaQuery<T> q = query.where(predicates.toArray(new Predicate[0]));
q = (CriteriaQuery<T>)extension.apply(q);
return orders.isEmpty() ? q : q.orderBy(orders.toArray(new Order[0]));
}
public CriteriaQuery<Long> resultCount() {
return resultCount(q -> q);
}
@SuppressWarnings("unchecked")
public CriteriaQuery<Long> resultCount(Function<CriteriaQuery<?>, CriteriaQuery<?>> extension) {
CriteriaQuery<Long> q = builder.createQuery(Long.class);
q.from(clazz).alias(alias);
q.where(predicates.toArray(new Predicate[0]));
if (q.isDistinct()) {
q.select(builder.countDistinct(root));
} else {
q.select(builder.count(root));
}
return (CriteriaQuery<Long>)extension.apply(q);
}
/**
* 条件句 ( or 条件含む ) を追加します。
* <p>引数には CriteriaBuilder で生成した Predicate を追加してください。
*/
public OrmCriteria<T> add(final Predicate predicate) {
this.predicates.add(predicate);
return this;
}
/** or 条件を付与します。 */
public OrmCriteria<T> or(final Predicate... predicates) {
if (predicates.length != 0) {
add(builder.or(predicates));
}
return this;
}
/** null 一致条件を付与します。 */
public OrmCriteria<T> isNull(String field) {
return add(builder.isNull(root.get(field)));
}
/** null 不一致条件を付与します。 */
public OrmCriteria<T> isNotNull(String field) {
return add(builder.isNotNull(root.get(field)));
}
/** 一致条件を付与します。( 値が null の時は無視されます ) */
public OrmCriteria<T> equal(String field, final Object value) {
return equal(root, field, value);
}
public OrmCriteria<T> equal(Path<?> path, String field, final Object value) {
if (isValid(value)) {
add(builder.equal(path.get(field), value));
}
return this;
}
private boolean isValid(final Object value) {
if (value instanceof String) {
return StringUtils.isNotBlank((String) value);
} else if (value instanceof Optional) {
return ((Optional<?>) value).isPresent();
} else {
return value != null;
}
}
/** 不一致条件を付与します。(値がnullの時は無視されます) */
public OrmCriteria<T> equalNot(String field, final Object value) {
if (isValid(value)) {
add(builder.notEqual(root.get(field), value));
}
return this;
}
/** 一致条件を付与します。(値がnullの時は無視されます) */
public OrmCriteria<T> equalProp(String field, final String fieldOther) {
add(builder.equal(root.get(field), root.get(fieldOther)));
return this;
}
/** like条件を付与します。(値がnullの時は無視されます) */
public OrmCriteria<T> like(String field, String value, MatchMode mode) {
if (isValid(value)) {
add(builder.like(root.get(field), mode.toMatchString(value)));
}
return this;
}
/** like条件を付与します。[複数フィールドに対するOR結合](値がnullの時は無視されます) */
public OrmCriteria<T> like(String[] fields, String value, MatchMode mode) {
if (isValid(value)) {
Predicate[] predicates = new Predicate[fields.length];
for (int i = 0; i < fields.length; i++) {
predicates[i] = builder.like(root.get(fields[i]), mode.toMatchString(value));
}
add(builder.or(predicates));
}
return this;
}
/** in条件を付与します。 */
public OrmCriteria<T> in(String field, final Object[] values) {
if (values != null && 0 < values.length) {
add(root.get(field).in(values));
}
return this;
}
/** between条件を付与します。 */
public OrmCriteria<T> between(String field, final Date from, final Date to) {
if (from != null && to != null) {
predicates.add(builder.between(root.get(field), from, to));
}
return this;
}
/** between条件を付与します。 */
public OrmCriteria<T> between(String field, final LocalDate from, final LocalDate to) {
if (from != null && to != null) {
predicates.add(builder.between(root.get(field), from, to));
}
return this;
}
/** between条件を付与します。 */
public OrmCriteria<T> between(String field, final LocalDateTime from, final LocalDateTime to) {
if (from != null && to != null) {
predicates.add(builder.between(root.get(field), from, to));
}
return this;
}
/** between条件を付与します。 */
public OrmCriteria<T> between(String field, final String from, final String to) {
if (isValid(from) && isValid(to)) {
predicates.add(builder.between(root.get(field), from, to));
}
return this;
}
/** [フィールド]>=[値] 条件を付与します。(値がnullの時は無視されます) */
public <Y extends Comparable<? super Y>> OrmCriteria<T> gte(String field, final Y value) {
if (isValid(value)) {
add(builder.greaterThanOrEqualTo(root.get(field), value));
}
return this;
}
/** [フィールド]>[値] 条件を付与します。(値がnullの時は無視されます) */
public <Y extends Comparable<? super Y>> OrmCriteria<T> gt(String field, final Y value) {
if (isValid(value)) {
add(builder.greaterThan(root.get(field), value));
}
return this;
}
/** [フィールド]<=[値] 条件を付与します。 */
public <Y extends Comparable<? super Y>> OrmCriteria<T> lte(String field, final Y value) {
if (isValid(value)) {
add(builder.lessThanOrEqualTo(root.get(field), value));
}
return this;
}
/** [フィールド]<[値] 条件を付与します。 */
public <Y extends Comparable<? super Y>> OrmCriteria<T> lt(String field, final Y value) {
if (isValid(value)) {
add(builder.lessThan(root.get(field), value));
}
return this;
}
/** ソート条件を加えます。 */
public OrmCriteria<T> sort(Sort sort) {
sort.getOrders().forEach(this::sort);
return this;
}
/** ソート条件を加えます。 */
public OrmCriteria<T> sort(SortOrder order) {
if (order.isAscending()) {
sort(order.getProperty());
} else {
sortDesc(order.getProperty());
}
return this;
}
/** 昇順条件を加えます。 */
public OrmCriteria<T> sort(String field) {
orders.add(builder.asc(root.get(field)));
return this;
}
/** 降順条件を加えます。 */
public OrmCriteria<T> sortDesc(String field) {
orders.add(builder.desc(root.get(field)));
return this;
}
public boolean emptySort() {
return !orders.isEmpty();
}
/** 指定した Entity クラスを軸にしたCriteriaを生成します。 */
public static <T> OrmCriteria<T> of(EntityManager em, Class<T> clazz) {
return new OrmCriteria<>(em, clazz, DefaultAlias);
}
/** 指定した Entity クラスにエイリアスを紐付けたCriteriaを生成します。 */
public static <T> OrmCriteria<T> of(EntityManager em, Class<T> clazz, String alias) {
return new OrmCriteria<>(em, clazz, alias);
}
}